/*
 
(c) 2008 by Sławek Kruczek
slav.mir@gmail.com
 

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/


package application;

import javax.swing.SwingWorker;
import analytics.util.BinaryFunction;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class Calculator extends SwingWorker<double[][][], Void> {

    private final BinaryFunction func;
    private final int range, detail, length, totalPoints;
    private int calculatedPoints = 0;
    
    private double[][][] result = null;
    
    // There can be only _one_ Calculator in operation at any given time.
    // This simplifies handling of cancelled Calculators and doesn't hurt
    // performace since they use all available processors anyway
    private static Lock lock = new ReentrantLock();
    
    

    public Calculator(BinaryFunction f, int range, int detail) {
        this.func = f;
        this.range = range;
        this.detail = detail;
        this.length = range * detail * 2;
        totalPoints = (length + 1) * (length + 1);
    }
    
    
    @Override
    protected double[][][] doInBackground() throws Exception {
        
        lock.lockInterruptibly();
        
        try {
            try {
                result = new double[length + 1][length + 1][3];
            }
            catch(OutOfMemoryError e) {
                //System.err.println("Out of memory...");
                return null;
            }

            Thread[] threads = new Thread[Runtime.getRuntime().availableProcessors()];

            //System.out.println("Starting " + threads.length + " thread(s).");

            int chunkLength = result.length / threads.length;

            for(int i = 0; i < threads.length; i++) {
                int start = i * chunkLength;
                int end = ((i == threads.length - 1) ? length + 1 : start + chunkLength);

                threads[i] = new Thread(new Leprechaun(start, end));
                threads[i].start();
            }

            for(int i = 0; i < threads.length; i++) {
                threads[i].join();
            }
        
            return result;
        }
        finally {
            lock.unlock();
        }
    }
    
    
    private synchronized void reportCalculated(int n)
    {
        calculatedPoints += n;
        setProgress((int)((float)calculatedPoints / totalPoints * 100));
    }
    
    
    // Does all the real work
    private class Leprechaun implements Runnable {
        
        private final int start, end;
        
        public Leprechaun(int start, int end) {
            this.start = start;
            this.end = end;
        }

        public void run() {
            // for each row k along the y axis
            for(int k = start; k < end && !isCancelled(); k++) {
                //for each column j along the x axis
                for(int j = 0; j <= length; j++) {
                    double x = (j - length / 2.0) / detail;
                    double y = (k - length / 2.0) / detail;
                    double z = func.eval(x, y);
                    
                    // derivatives ;)
                    double delta = 1.0 / detail / 100000;
                    double dx = (func.eval(x + delta, y) - z) / delta;
                    double dy = (func.eval(x, y + delta) - z) / delta;
                    
                    result[k][j][0] = z;
                    result[k][j][1] = dx;
                    result[k][j][2] = dy;
                    
                    
                }
                
                reportCalculated(length + 1);
            }
        }
    }
}
